﻿using System;
using System.Diagnostics.Contracts;
using UnitOfWork.EntityFramework.Configuration;
using UnitOfWork.Interfaces;
using Utility.Locators;

namespace UnitOfWork.EntityFramework
{
	[ContractClass(typeof(UnitOfWorkFactoryBaseContract))]
	public abstract class UnitOfWorkFactoryBase : IUnitOfWorkFactory
	{
		private readonly ILocator<ILinqProvider> _linqProviderLocator;
		private readonly IConfigurationProvider _configurationProvider;

		protected UnitOfWorkFactoryBase(ILocator<ILinqProvider> linqProviderLocator,
			IConfigurationProvider configurationProvider)
		{
			Contract.Requires(linqProviderLocator != null);
			Contract.Requires(configurationProvider != null);

			_linqProviderLocator = linqProviderLocator;
			_configurationProvider = configurationProvider;
		}

		public IUnitOfWork StartNewLightWeight()
		{
			if (_lightWeightInstance != null)
				throw new UnitOfWorkFactoryException("Нельзя повторно создать облегченный контекст для чтения, когда он уже создан. Он создается один раз (например, в начале программы или в App_Start), и за его освобождение отвечает создавший.");

			_lightWeightInstance = CreateLightWeight(_configurationProvider.ConnectionStringName);
			_linqProviderLocator.SetCurrent(_lightWeightInstance);
			return _lightWeightInstance;
		}

		public IUnitOfWork StartNew()
		{
			if (_linqProviderLocator.HasCurrent && _linqProviderLocator.GetCurrent() != _lightWeightInstance)
				throw new UnitOfWorkFactoryException("Нельзя создавать более одной вложенной единицы работы. Организуйте вызовы так, чтобы корневая единица работы использовалась всеми дочерними методами. ");

			_fullInstance = Create(_configurationProvider.ConnectionStringName);

			var wrappedInstance = _linqProviderLocator.HasCurrent // после закрытия этой новой единицы работы восстановится ЕР «по умолчанию» - облегченная
				? new UnitOfWorkDisposeDecorator(_fullInstance, () => _linqProviderLocator.SetCurrent(_lightWeightInstance)) 
				: _fullInstance;

			_linqProviderLocator.SetCurrent(wrappedInstance);
			return _fullInstance;
		}

		protected abstract IUnitOfWork Create(string connectionStringName);
		protected virtual IUnitOfWork CreateLightWeight(string connectionStringName)
		{
			return Create(connectionStringName);
		}

		private IUnitOfWork _lightWeightInstance = null;
		private IUnitOfWork _fullInstance = null;
	}

	public class UnitOfWorkFactoryException : ApplicationException
	{
		public UnitOfWorkFactoryException(string message) : base(message){ }
	}

	[ContractClassFor(typeof(UnitOfWorkFactoryBase))]
	public abstract class UnitOfWorkFactoryBaseContract : UnitOfWorkFactoryBase
	{
		protected UnitOfWorkFactoryBaseContract(ILocator<ILinqProvider> linqProviderLocator, IConfigurationProvider configurationProvider)
			: base(linqProviderLocator, configurationProvider)
		{
		}

		protected override IUnitOfWork Create(string connectionStringName)
		{
			Contract.Ensures(Contract.Result<IUnitOfWork>() != null);

			throw new NotSupportedException();
		}
	}
}